﻿
Imports System.Net.Sockets
Imports osi.root.procedure
Imports osi.root.delegates
Imports osi.root.formation
Imports osi.root.utils
Imports osi.root.connector
Imports osi.service.device
Imports osi.service.commander
Imports envs = osi.root.envs

Partial Public Class powerpoint
    Private Class tcp_client_flow
        Implements flow

        Private Class tcp_client_poller
            Private Shared ReadOnly ms As Int64
            Private ReadOnly c As TcpClient
            Private ReadOnly p As powerpoint
            Private last_poll_alive_ms As Int64

            Shared Sub New()
                ms = constants.interval_ms.watch_connectivity
            End Sub

            Public Sub New(ByVal c As TcpClient,
                           ByVal p As powerpoint)
                assert(Not c Is Nothing)
                assert(Not p Is Nothing)
                Me.c = c
                Me.p = p
                Me.last_poll_alive_ms = nowadays.milliseconds()
            End Sub

            Public Function poll() As Boolean
                Dim nms As Int64 = 0
                nms = nowadays.milliseconds()
                If nms - last_poll_alive_ms >= ms Then
                    If Not free_poll_alive(c, p) Then
                        Return False
                    End If
                    last_poll_alive_ms = nms
                End If
                Return True
            End Function
        End Class

        Private Class tcp_client_indicator
            Implements indicator

            Private ReadOnly c As TcpClient
            Private ReadOnly poller As tcp_client_poller

            Public Sub New(ByVal c As TcpClient,
                           ByVal p As powerpoint)
                assert(Not c Is Nothing)
                Me.c = c
                Me.poller = New tcp_client_poller(c, p)
            End Sub

            Public Function indicate(ByVal pending As pointer(Of Boolean)) _
                                    As event_comb Implements indicator.indicate
                Return sync_async(Function() As Boolean
                                      If Not c Is Nothing AndAlso
                                         c.alive() Then
                                          Dim b As Int32 = 0
                                          b = c.buffered_bytes()
                                          If b < 0 Then
                                              Return False
                                          Else
                                              eva(pending, b > 0)
                                              Return poller.poll()
                                          End If
                                      Else
                                          Return False
                                      End If
                                  End Function)
            End Function
        End Class

        Private Class tcp_client_sensor
            Implements sensor

            Private Shared ReadOnly buff() As Byte
            Private Shared ReadOnly buff_size As Int32
            Private ReadOnly c As TcpClient
            Private ReadOnly poller As tcp_client_poller

            Shared Sub New()
                buff_size = 1
                ReDim buff(buff_size - 1)
            End Sub

            Public Sub New(ByVal c As TcpClient,
                           ByVal p As powerpoint)
                assert(Not c Is Nothing)
                Me.c = c
                Me.poller = New tcp_client_poller(c, p)
            End Sub

            Public Function sense(ByVal pending As pointer(Of Boolean),
                                  ByVal timeout_ms As Int64) As event_comb Implements sensor.sense
                Dim ec As event_comb = Nothing
                Dim result As pointer(Of Int32) = Nothing
                Return New event_comb(Function() As Boolean
                                          If poller.poll() Then
                                              result = New pointer(Of Int32)()
                                              ec = event_comb_async_operation.ctor(
                                                       Function(ac As AsyncCallback) As IAsyncResult
                                                           Return c.Client().BeginReceive(buff,
                                                                                          0,
                                                                                          buff_size,
                                                                                          SocketFlags.Peek,
                                                                                          ac,
                                                                                          Nothing)
                                                       End Function,
                                                       Function(ar As IAsyncResult) As Int32
                                                           Return c.Client().EndReceive(ar)
                                                       End Function,
                                                       result)
                                              Return waitfor(ec, timeout_ms) AndAlso
                                                     goto_next()
                                          Else
                                              Return False
                                          End If
                                      End Function,
                                      Function() As Boolean
                                          Return eva(pending,
                                                     ec.end_result() AndAlso
                                                     ((+result) = buff_size)) AndAlso
                                                 goto_end()
                                      End Function)
            End Function
        End Class

        Public ReadOnly c As TcpClient
        Private ReadOnly p As powerpoint
        Private ReadOnly sensor As sensor
        Private ReadOnly sf As stream_flow_adapter
        Private ReadOnly single_send_size As UInt32
        Private last_send_ms As Int64
        Private last_send_size As UInt32
        Private last_receive_ms As Int64
        Private last_receive_size As UInt32

        Public Sub New(ByVal c As TcpClient,
                       ByVal p As powerpoint)
            assert(Not c Is Nothing)
            assert(Not p Is Nothing)
            Me.c = c
            Me.p = p
            Me.sensor = If(constants.use_socket,
                           New indicator_sensor_adapter(New tcp_client_indicator(c, p)),
                           New tcp_client_sensor(c, p))
            If Not constants.use_socket Then
                sf = New stream_flow_adapter(c.stream(), p.send_rate_sec, p.receive_rate_sec, False)
            End If
            single_send_size = c.send_buff_size()
        End Sub

        Public Function use_socket() As Boolean
            Return sf Is Nothing
        End Function

        Public Function use_stream() As Boolean
            Return Not sf Is Nothing
        End Function

        Private Function finish(ByVal ec As event_comb, ByVal m As String) As Boolean
            If ec.end_result() Then
                Return goto_end()
            Else
                If envs.tcp_trace Then
                    raise_error("failed to ",
                                m,
                                " data to ",
                                c.identity(),
                                " @ ",
                                p.identity())
                End If
                c.shutdown()
                Return False
            End If
        End Function

        Private Function timeouted(ByVal last_ms As Int64,
                                   ByVal rate_sec As UInt32,
                                   ByVal last_size As UInt32) As Boolean
            Return rate_sec > 0 AndAlso
                   nowadays.milliseconds() > last_ms AndAlso
                   rate_sec * (nowadays.milliseconds() - last_ms) > last_size
        End Function

        Private Function socket_send(ByVal buff() As Byte,
                                     ByVal offset As UInt32,
                                     ByVal count As UInt32,
                                     ByVal sent As pointer(Of UInt32)) As event_comb
            assert(use_socket())
            Return sync_async(Function() As Boolean
                                  If Not c.alive() OrElse
                                     single_send_size = 0 OrElse
                                     array_size(buff) < offset + count Then
                                      Return False
                                  ElseIf count = 0 Then
                                      Return True
                                  ElseIf c.poll_write() Then
                                      Dim size As UInt32 = 0
                                      size = min(count, single_send_size)
                                      assert(size > 0)
                                      Try
                                          assert(c.Client().Send(buff, offset, size, SocketFlags.None) = size)
                                      Catch
                                          Return False
                                      End Try
                                      last_send_ms = nowadays.milliseconds()
                                      last_send_size = size
                                      Return eva(sent, size)
                                  Else
                                      Return Not timeouted(last_send_ms, p.send_rate_sec, last_send_size) AndAlso
                                             eva(sent, 0)
                                  End If
                              End Function)
        End Function

        Private Function stream_send(ByVal buff() As Byte,
                                     ByVal offset As UInt32,
                                     ByVal count As UInt32,
                                     ByVal sent As pointer(Of UInt32)) As event_comb
            assert(use_stream())
            Return sf.send(buff, offset, count, sent)
        End Function

        Public Function send(ByVal buff() As Byte,
                             ByVal offset As UInt32,
                             ByVal count As UInt32,
                             ByVal sent As pointer(Of UInt32)) As event_comb Implements flow.send
            Dim ec As event_comb = Nothing
            Return New event_comb(Function() As Boolean
                                      If use_stream() Then
                                          ec = stream_send(buff, offset, count, sent)
                                      ElseIf use_socket() Then
                                          ec = socket_send(buff, offset, count, sent)
                                      Else
                                          Return assert(False)
                                      End If
                                      assert(Not ec Is Nothing)
                                      Return waitfor(ec, rate_to_ms(p.send_rate_sec, count)) AndAlso
                                             goto_next()
                                  End Function,
                                  Function() As Boolean
                                      If envs.tcp_detail_trace Then
                                          raise_error(c.identity(), " sent ", bytes_str(buff, offset, +sent))
                                      End If
                                      Return finish(ec, "send")
                                  End Function)
        End Function

        Private Function socket_receive(ByVal buff() As Byte,
                                        ByVal offset As UInt32,
                                        ByVal count As UInt32,
                                        ByVal result As pointer(Of UInt32)) As event_comb
            Return sync_async(Function() As Boolean
                                  If Not c.alive() Then
                                      Return False
                                  ElseIf array_size(buff) < offset + count Then
                                      Return False
                                  ElseIf count = 0 Then
                                      Return True
                                  Else
                                      Dim b As Int32 = 0
                                      b = c.buffered_bytes()
                                      If b < 0 Then
                                          Return False
                                      ElseIf b > 0 Then
                                          Dim size As UInt32 = 0
                                          size = min(count, CUInt(b))
                                          assert(size > 0)
                                          Try
                                              assert(c.Client().Receive(buff, offset, size, SocketFlags.None) = size)
                                          Catch
                                              Return False
                                          End Try
                                          last_receive_size = size
                                          last_receive_ms = nowadays.milliseconds()
                                          Return eva(result, size)
                                      Else
                                          Return Not timeouted(last_receive_ms,
                                                               p.receive_rate_sec,
                                                               last_receive_size) AndAlso
                                                 eva(result, 0)
                                      End If
                                  End If
                              End Function)
        End Function

        Private Function stream_receive(ByVal buff() As Byte,
                                        ByVal offset As UInt32,
                                        ByVal count As UInt32,
                                        ByVal result As pointer(Of UInt32)) As event_comb
            assert(use_stream())
            Return sf.receive(buff, offset, count, result)
        End Function

        Public Function receive(ByVal buff() As Byte,
                                ByVal offset As UInt32,
                                ByVal count As UInt32,
                                ByVal result As pointer(Of UInt32)) As event_comb _
                               Implements flow.receive
            Dim ec As event_comb = Nothing
            Return New event_comb(Function() As Boolean
                                      If use_stream() Then
                                          ec = stream_receive(buff, offset, count, result)
                                      ElseIf use_socket() Then
                                          ec = socket_receive(buff, offset, count, result)
                                      Else
                                          Return assert(False)
                                      End If
                                      Return waitfor(ec, rate_to_ms(p.receive_rate_sec, count)) AndAlso
                                             goto_next()
                                  End Function,
                                  Function() As Boolean
                                      If envs.tcp_detail_trace Then
                                          raise_error(c.identity(), " received ", bytes_str(buff, offset, +result))
                                      End If
                                      Return finish(ec, "receive")
                                  End Function)
        End Function

        Public Function sense(ByVal pending As pointer(Of Boolean),
                              ByVal timeout_ms As Int64) As event_comb Implements flow.sense
            Return sensor.sense(pending, timeout_ms)
        End Function
    End Class
End Class
